﻿//-----------------------------------------------------------------------
// <copyright file="ComponentProvider.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Base.Component
{
    using System;
    using System.Collections.Generic;
    using TileTool.Component;
    using TileTool.Log;
    using TileTool.Threading.Future;
    using TileTool.Threading.Future.Generic;
    using TileTool.Threading.Job;

    /// <summary>
    /// IComponentProvider implementation.
    /// </summary>
    public class ComponentProvider : IComponentProvider
    {
        #region Fields - Private

        /// <summary>
        /// All components we know about.
        /// </summary>
        private IEnumerable<IComponent> allComponents;

        /// <summary>
        /// Dictionary which maps from a component name to a component.
        /// </summary>
        private IDictionary<string, IComponent> nameDict = new Dictionary<string, IComponent>();

        #endregion

        #region Properties - Public

        /// <summary>
        /// Gets an enumeration of all components the provider knows about.
        /// </summary>
        public IEnumerable<IComponent> AllComponents
        {
            get
            {
                return this.allComponents;
            }

            private set
            {
                this.allComponents = value;
            }
        }

        #endregion

        #region Methods - Public

        /// <summary>
        /// Called to tell the component provider to collect all components.
        /// </summary>
        /// <returns>Returns a future object which tracks the state of the collection process.</returns>
        public IFuture Collect()
        {
            ComponentCollectorJob job = new ComponentCollectorJob();

            JobSystem.Submit(job);

            job.Future.OnComplete(this.HandleComponentCollectionComplete);

            return job.Future;
        }

        /// <summary>
        /// Get all components which are of the passed type.
        /// </summary>
        /// <typeparam name="T">Type of component to get.</typeparam>
        /// <returns>Returns an enumeration of all components of the provided type.</returns>
        public IEnumerable<T> GetComponents<T>() where T : class, IComponent
        {
            IList<T> res = new List<T>();

            foreach (IComponent comp in this.AllComponents)
            {
                Type compType = comp.GetType();
                if (!compType.IsInstanceOfType(typeof(T)))
                {
                    continue;
                }

                res.Add(comp as T);
            }

            return res;
        }

        /// <summary>
        /// Get a component by the passed name.
        /// </summary>
        /// <param name="name">Name of the component to locate.</param>
        /// <returns>Returns the component with the passed name or null if none was found.</returns>
        public IComponent GetComponentByName(string name)
        {
            IComponent res = null;

            if (string.IsNullOrEmpty(name))
            {
                return res;
            }

            this.nameDict.TryGetValue(name, out res);

            return res;
        }

        /// <summary>
        /// Get a component by the passed name.
        /// </summary>
        /// <typeparam name="T">Type of component to get.</typeparam>
        /// <param name="name">Name of the component to locate.</param>
        /// <returns>Returns the component with the passed name or null if none was found.</returns>
        public T GetComponentByName<T>(string name) where T : class, IComponent
        {
            return this.GetComponentByName(name) as T;
        }

        #endregion

        #region Methods - Private

        /// <summary>
        /// Set the components that the provider knows about and build any
        /// supporting data structures.
        /// </summary>
        /// <param name="components">Components that were collected.</param>
        private void SetComponents(IEnumerable<IComponent> components)
        {
            this.AllComponents = components;

            foreach (IComponent comp in this.AllComponents)
            {
                Type compType = comp.GetType();

                if (string.IsNullOrEmpty(comp.Name))
                {
                    LogSystem.LogError("Component has a null name: {0} - {1}", compType.AssemblyQualifiedName, compType.Assembly.Location);
                    continue;
                }

                IComponent existing = null;
                if (this.nameDict.TryGetValue(comp.Name, out existing))
                {
                    LogSystem.LogError("Duplicate component name found: {0} - {1}", comp.Name, compType.Assembly.Location);

                    Type existingType = existing.GetType();
                    LogSystem.LogError("Current component with name {0} - {1}", comp.Name, existingType.Assembly.Location);

                    continue;
                }

                this.nameDict.Add(comp.Name, comp);
            }
        }

        /// <summary>
        /// Handle completion of the component loading.
        /// </summary>
        /// <param name="future">Future which contains our results.</param>
        private void HandleComponentCollectionComplete(IFuture<IEnumerable<IComponent>> future)
        {
            if (future.IsCompleted)
            {
                this.SetComponents(future.Result);
            }

            // $TODO - error cases?
        }

        #endregion
    }
}
